#!/bin/bash

#$1 is an action, like install
#$2 is app name, like Arduino

DIRECTORY="$(readlink -f "$(dirname "$0")")"

function error {
  echo -e "\e[91m$1\e[39m"
  exit 1
}

if [ -z "$1" ];then
  error "You need to specify an operation, and in most cases, which app to operate on."
fi

set -a #make all functions in the api available to apps
source "${DIRECTORY}/api" || error "failed to source ${DIRECTORY}/api"

#remove old mcpi repositories - this runonce is here so that terminal-only users will still receive the fix.
(runonce <<"EOF"
  if [ -f /etc/apt/sources.list.d/mcpi-revival.list ] && cat /etc/apt/sources.list.d/mcpi-revival.list | grep -qF 'https://mcpirevival.tk/mcpi-packages' ;then
    echo 'deb [trusted=yes] https://mcpi-revival.github.io/mcpi-packages/ buster main' | sudo tee /etc/apt/sources.list.d/mcpi-revival.list
  elif [ -f /etc/apt/sources.list.d/mcpi-revival.list ] && cat /etc/apt/sources.list.d/mcpi-revival.list | grep -qF 'https://mcpi-revival.github.io/mcpi-packages/debs' ;then
    echo 'deb [trusted=yes] https://mcpi-revival.github.io/mcpi-packages/ buster main' | sudo tee /etc/apt/sources.list.d/mcpi-revival.list
  fi
  if [ -f /etc/apt/sources.list.d/Alvarito050506_mcpi-devs.list ];then
    sudo rm -f /etc/apt/sources.list.d/Alvarito050506_mcpi-devs.list
  fi
  
  if dpkg -l box86-no-binfmt-restart &>/dev/null ;then
    sudo apt purge -y box86-no-binfmt-restart
    sudo apt update
    sudo apt install -y box86
  fi
EOF
) &>/dev/null

#Silently re-download repo if github repository is over 3 months out of date
{
current_git_date="$(cd "$DIRECTORY"; git show -s --format=%ct)"
if [ $(date +%s) -gt $(($current_git_date + 7776000)) ];then
  yad --window-icon="${DIRECTORY}/icons/logo.png"  --width=500 --no-buttons --center --title="Auto-updating Pi-Apps" \
    --text="Your Pi-Apps github repository is somehow 3 months out-of-date."$'\n'"Downloading fresh version of pi-apps and saving the old version to ${DIRECTORY}-3-months-old..." &
  sleep 1
  clear
  echo -e "\nYour Pi-Apps github repository is somehow 3 months out-of-date.\nDownloading fresh version of pi-apps and saving the old version to ${DIRECTORY}-3-months-old...\n\n" 1>&2
  cd $HOME
  rm -rf ~/pi-apps-forced-update
  git clone "$(cat "${DIRECTORY}/etc/git_url")" pi-apps-forced-update 1>&2 && cp -af "${DIRECTORY}/data" ~/pi-apps-forced-update && mv -f "$DIRECTORY" "${DIRECTORY}-3-months-old" && mv -f ~/pi-apps-forced-update "${DIRECTORY}"
  temp_logfile="$(mktemp).txt"
  echo -e "Updated a 3-months-outdated pi-apps install.\nFrom: $current_git_date\nTo: $(cd "$DIRECTORY"; git show -s --format=%ct)\n$(get_device_info)" >> "$temp_logfile"
  send_error_report "$temp_logfile" 1>&2
  rm -f "$temp_logfile"
  sleep 10
fi
}

mkdir -p "${DIRECTORY}/data/status" "${DIRECTORY}/data/update-status" "${DIRECTORY}/logs"

#remove week-old logfiles
find "${DIRECTORY}/logs" -type f -mtime +7 -exec rm -f {} \; &>/dev/null &

#check for internet connection
if ! wget --spider github.com &>/dev/null ;then
  error "No internet connection!\ngithub.com failed to respond.\nErrors: $(wget --spider github.com 2>&1)"
fi

#check if hardware and OS is supported
if is_supported_system >/dev/null;then
  supported=yes
else
  supported=no
fi

if [ "$1" == 'multi-uninstall' ] || [ "$1" == 'multi-install' ];then
  
  if [ "$1" == 'multi-uninstall' ];then
    action=uninstall
  elif [ "$1" == 'multi-install' ];then
    action=install
  fi
  
  app_list="$2" #newline-separated list of apps to install/uninstall
  
  #if trying to install an already-installed app, or trying to uninstall and already-uninstalled app, ask for confirmation
  IFS=$'\n'
  for app in $app_list ;do
    if [ "$(app_status "${app}")" == "${action}ed" ];then
      yad --text="$app is already ${action}ed. Are you sure you want to $action it again?" \
        --text-align=center --center --title='Quick question' --window-icon="${DIRECTORY}/icons/logo.png" \
        --button=No!"${DIRECTORY}/icons/exit.png":1 --button=Yes!"${DIRECTORY}/icons/check.png":0
      
      if [ $? != 0 ];then
        #user clicked No, so remove the app from list
        app_list="$(echo "$app_list" | grep -vx "$app")"
      fi
    fi
  done
  
  #install/uninstall one app at a time. If it fails then add the app to the list of failed apps
  failed_apps=''
  for app in $app_list ;do
    
    "${DIRECTORY}/manage" $action "$app"
    if [ $? != 0 ];then
      #this app failed to install - add it to the list of failed apps
      failed_apps="$failed_apps
$app"
    fi
  done
  failed_apps="${failed_apps:1}" #remove first blank newline
  
  #diagnose every failed app's logfile
  for app in $(echo "$failed_apps") ;do
    logfile="$(get_logfile "$app")"
    #given the app's logfile, categorize the error and set the error_type variable
    diagnosis="$(log_diagnose "$logfile")"
    
    error_type="$(echo "$diagnosis" | head -n1)" #first line of diagnosis is the type of error
    error_caption="$(echo "$diagnosis" | tail -n +2)" #subsequent lines of diagnosis are caption(s)
    
    #set the window-text
    if [ "$error_type" == unknown ];then
      text="<b>${app^}</b> failed to $action for an <b>unknown</b> reason."
    else
      text="<b>${app^}</b> failed to $action because Pi-Apps encountered $([[ "$error_type" == [aeiou]* ]] && echo an || echo a) <b>$error_type</b> error."
    fi
    
    #if the error_type is NOT system, internet, or package, AND the app exists in the official Pi-Apps repository, AND the system setup is supported, then enable the "Send report button"
    if ! [[ "$error_type" =~ ^(system|internet|package)$ ]] && list_apps online | grep -q "$app" && [ "$supported" == yes ];then
      send_button=(--button='Send report'!"${DIRECTORY}/icons/upload.png":2)
    else
      #inform user why 'send report' button is missing
      text+=$'\n'"Your 'Send report' button is gone because "
      if [[ "$error_type" =~ ^(system|internet|package)$ ]];then
        text+="it's not an issue with Pi-Apps."
      elif ! list_apps online | grep -q "$app" ;then
        text+="this app is not in the official repository."
      elif [ "$supported" == no ];then
        text+="your system is unsupported."
      fi
    fi
    text+=$'\n'"Support is available on <a href=\"https://discord.gg/RXSTvaUvuu\">Discord</a> and <a href=\"https://github.com/Botspot/pi-apps/issues/new/choose\">Github</a>."
    
    #if the error_caption is empty, display logfile instead
    if [ -z "$error_caption" ];then
      text+=$'\n'"You can view the $([ "$action" == install ] && echo 'installation' || echo 'uninstall') output below."
      error_caption="$(cat "$logfile")"
    else
      text+=$'\n'"Below, Pi-Apps explains the error."
    fi
    
    echo "$error_caption" | yad "${yadflags[@]}" --text-info --width=700 --height=300 --title="Error occured when ${action}ing $app" \
      --image="${DIRECTORY}/icons/error.png" --image-on-top \
      --text="$text" --fontname=12 \
      --button='View log'!"${DIRECTORY}/icons/log-file.png"!"Review the output from when <b>$app</b> tried to $action.":"bash -c 'view_file "\""$logfile"\""'" \
      "${send_button[@]}" \
      --button=Close
    button=$?
    
    if [ $button == 2 ];then
      #send error log
      send_error_report "$logfile"
    fi
  done
  
  if [ ! -z "$failed_apps" ];then
    exit 1
  fi
  
elif [ "$1" == 'install-if-not-installed' ];then
  
  #if not installed
  if [ "$(app_status "$2")" != installed ];then
    #install it
    "${DIRECTORY}/manage" install "$2" || exit 1
  fi
  
elif [ "$1" == 'install' ] || [ "$1" == 'uninstall' ];then
  #for this operation, a program name must be specified.
  app="$2"
  if [ -z "$app" ];then
    error "For this operation, you must specify which app to operate on."
  elif [ ! -d "${DIRECTORY}/apps/$app" ];then
    error "${DIRECTORY}/apps/$app does not exist!"
  fi
  
  if [ "$1" == install ];then
    mode=install
  else
    mode=uninstall
  fi
  
  #ensure not a disabled app
  if [ "$mode" == install ] && [ "$(app_status "${app}")" == 'disabled' ];then
    warning "Not installing the $app app. IT IS DISABLED."
    exit 0
  fi
  
  #display warning if hardware and os is unsupported
  if [ "$supported" == no ];then
    warning "YOUR SYSTEM IS UNSUPPORTED:\n$(is_supported_system)"
    sleep 1
    echo -e "\e[103m\e[30mThe ability to send error reports has been disabled.\e[39m\e[49m"
    sleep 1
    echo -e "\e[103m\e[30mWaiting 10 seconds... (To cancel, press Ctrl+C or close this terminal)\e[39m\e[49m"
    sleep 10
  fi
  
  #determine path for log file to be created
  logfile="${DIRECTORY}/logs/${mode}-incomplete-${app}.log"
  if [ -f "$logfile" ] || [ -f "$(echo "$logfile" | sed 's+-incomplete-+-success-+g')" ] || [ -f "$(echo "$logfile" | sed 's+-incomplete-+-fail-+g')" ];then
    #append a number to logfile's file-extension if the original filename already exists
    i=1
    while true;do
      #if variable $i is 2, then example newlogfile value: /path/to/install-Discord.log2
      newlogfile="$logfile$i"
      if [ ! -f "$newlogfile" ] && [ ! -f "$(echo "$newlogfile" | sed 's+/-incomplete-+-success-+g')" ] && [ ! -f "$(echo "$newlogfile" | sed 's+-incomplete-+-fail-+g')" ];then
        logfile="${newlogfile}"
        break
      fi
      i=$((i+1))
    done
  fi
  
  #analytics
  bitly_link "$app" "$mode" &
  
  #if this app has scripts, determine which script to run
  if [ "$(app_type "$app")" == standard ];then
    if [ "$mode" == install ];then
      scriptname="$(script_name_cpu "$app")" #will be install, install-32, or install-64
      [ -z "$scriptname" ] && error "It appears $app does not have an install-${arch} script suitable for your ${arch}-bit OS."
    else #uninstall mode
      scriptname=uninstall
    fi
    appscript=("${DIRECTORY}/apps/${app}/${scriptname}")
    chmod u+x "$appscript" &>/dev/null
  #if this app just lists a package-name, set the appscript to install that package
  else
    appscript=(bash -c -o pipefail "apt_lock_wait ; sudo -E apt $(echo "$action" | sed 's/uninstall/purge/g') -yf $(cat "${DIRECTORY}/apps/$app/packages") 2>&1 | less_apt")
  fi
  
  status "${mode^}ing \e[1m${app}\e[0m\e[96m..." | tee -a "$logfile"
  echo
  cd $HOME
  if [[ "$3" == "update" ]]; then
    script_input="update"
  else
    script_input=""
  fi
  nice "${appscript[@]}" "$script_input" &> >(tee -a "$logfile")
  exitcode="${PIPESTATUS[0]}"
  #if app script succeeded
  if [ $exitcode == 0 ];then
    status_green "\n${mode^}ed ${app} successfully." | tee -a "$logfile"
    
    format_logfile "$logfile" #remove escape sequences from logfile
    mv "$logfile" "$(echo "$logfile" | sed 's+-incomplete-+-success-+g')" #
    echo
  else
    echo -e  "\n\e[91mFailed to ${mode} ${app}!\e[39m
\e[40m\e[93m\e[5m🔺\e[25m\e[39m\e[49m\e[93mNeed help? Copy the \e[1mENTIRE\e[0m\e[49m\e[93m terminal output or take a screenshot.
Please ask on Github: \e[94m\e[4mhttps://github.com/Botspot/pi-apps/issues/new/choose\e[24m\e[93m
Or on Discord: \e[94m\e[4mhttps://discord.gg/RXSTvaUvuu\e[0m" | tee -a "$logfile"
    
    #set the app's status to 'corrupted' if the diagnostics determine the error is NOT internet, system, package
    if ! [[ "$(log_diagnose "$logfile" | head -n1)" =~ ^(system|internet|package)$ ]];then
      echo "corrupted" > "${DIRECTORY}/data/status/${app}"
    fi
    
    format_logfile "$logfile" #remove escape sequences from logfile
    mv "$logfile" "$(echo "$logfile" | sed 's+-incomplete-+-fail-+g')"
  fi
  
  #determine what status the app should now be
  if [ "$(app_type "$app")" == standard ];then
    #if the app is standard, and succeeded, mark it as installed/uninstalled
    if [ "$exitcode" == 0 ];then
      echo "${mode}ed" > "${DIRECTORY}/data/status/${app}"
    #if the app is standard and failed, AND diagnostics don't determine it to be internet/system/package, mark it as corrupted
    else
      #set the app's status to 'corrupted' if diagnostics determine that the error is NOT internet, system, package
      if ! [[ "$(log_diagnose "$logfile" | head -n1)" =~ ^(system|internet|package)$ ]];then
        echo "corrupted" > "${DIRECTORY}/data/status/${app}"
      fi
    fi
  #if the app is a package, set its status to whatever dpkg thinks it is
  elif [ "$(app_type "$app")" == package ];then
    refresh_pkgapp_status "$app"
  fi
  
  #exit the manage script with the same exit-code of the app's script
  exit $exitcode
  
elif [ "$1" == 'update' ];then
  #UPDATE
  #for this operation, a program name must be specified.
  app="$2"
  if [ -z "$app" ];then
    error "For this operation, you must specify which app to operate on."
  fi
  
  #detect which installation script exists and get the hash for that one
  scriptname="$(script_name_cpu "$app")"
  [ $? == 1 ] && exit 1
  
  status "Updating \e[1m${app}\e[0m\e[96m..."
  echo
  
  #HIDDEN FEATURE - if $3 equals nofetch, then don't download github repo. Useful for updating multiple apps at maximum speed
  if [ "$3" == 'nofetch' ] && [ -d "${DIRECTORY}/update" ];then
    true
  else
    rm -rf "${DIRECTORY}/update" && mkdir "${DIRECTORY}/update" && cd "${DIRECTORY}/update" || error "failed to enter the update directory!"
    git clone --depth=1 "$(cat "${DIRECTORY}/etc/git_url")" || error "failed to clone repository!"
  fi
  
  echo
  
  newinstallhash="$(sha1sum "${DIRECTORY}/update/pi-apps/apps/${app}/${scriptname}" 2>/dev/null | awk '{print $1}')"
  oldinstallhash="$(sha1sum "${DIRECTORY}/apps/${app}/${scriptname}" 2>/dev/null | awk '{print $1}')"
  
  #echo -e "newinstallhash: $newinstallhash\noldinstallhash: $oldinstallhash"
  #echo -e "newhash: $newhash\noldhash: $oldhash"
  
  if diff -r "${DIRECTORY}/apps/${app}" "${DIRECTORY}/update/pi-apps/apps/${app}" -q >/dev/null;then
    status_green "$app is identical to the online version. Nothing to do!"
    echo
    exit 0
  fi
  #else
  status_green "$app is not identical to the online version."
  installback=no
  
  echo
  
  #if install script was changed                                                    #if installed already
  if [ "$newinstallhash" != "$oldinstallhash" ] && [ "$(app_status "${app}")" == 'installed' ];then
    installback=yes
    status "$app's install script has been updated. Reinstalling $app..."
    #uninstall it using a recursive script instance
    #report to the app uninstall script that this is an uninstall for the purpose of updating by passing "update"
    "${DIRECTORY}/manage" uninstall "$app" "update"
    
    #fix edge case: if app is installed but uninstall script doesn't exist somehow, then pretend app was uninstalled so that the reinstall later will happen noninteractively
    if [ "$(app_status "$app")" == installed ];then
      echo 'uninstalled' > "${DIRECTORY}/data/status/${app}"
    fi
  fi
  
  #move old program to trash
  gio trash "${DIRECTORY}/apps/${app}" 2>/dev/null
  #failsafe
  [ -d "${DIRECTORY}/apps/${app}" ] && rm -rf "${DIRECTORY}/apps/${app}"
  
  #copy new version from update/ to apps/
  cp -rf "${DIRECTORY}/update/pi-apps/apps/${app}" "${DIRECTORY}/apps/${app}"
  
  if [ "$installback" == 'yes' ];then
    #install it using a recursive script instance
    "${DIRECTORY}/manage" install "$app"
    if [ $? != 0 ]; then
      failed=true
    fi
  fi
  
  if [ "$failed" == 'true' ]; then
    echo -e "\e[91mFailed to update ${app}.\e[0m"
  else
    status_green "${app} was updated successfully."
  fi
  echo
  
elif [ "$1" == 'check-all' ];then
  
  #CHECK-ALL
  #for this operation, a program name cannot be specified.
  
  #hidden flags
  installedonly=0
  if [ "$2" == 'installedonly' ];then
    # If $2 is 'installedonly', then only check for updates for those apps that are installed
    installedonly=1
  fi #end of checking for hidden flags
  
  echo -n "Checking for online changes... " 1>&2
  
  #if the updater script exists in update folder, then just git pull to save time
  if [ -f "${DIRECTORY}/update/pi-apps/updater" ];then
    cd "${DIRECTORY}/update/pi-apps"
    git pull || rm -rf "${DIRECTORY}/update"
  fi | grep -v "Already up to date." 1>&2
  
  #re-check and if updater script does not exist then do a git clone
  if [ ! -f "${DIRECTORY}/update/pi-apps/updater" ];then
    rm -rf "${DIRECTORY}/update"
    mkdir -p "${DIRECTORY}/update" && cd "${DIRECTORY}/update" || error "failed to enter the update directory!"
    git clone --depth=1 "$(cat "${DIRECTORY}/etc/git_url")" 1>&2 || error "failed to clone repository to the update directory!"
  fi
  
  echo "Done" 1>&2
  
  if [ $installedonly == 1 ];then
    #installedonly flag enabled. Remove apps that are uninstalled, disabled, or corrupted
    applist="$(list_apps installed)"
  else
    #installedonly flag disabled, so use entire combined list of apps, both local and online
    applist="$(list_apps all)"
  fi
  
  #echo "App list: $applist" 1>&2
  
  updatable=''
  IFS=$'\n'
  for app in $applist
  do
    echo -en "Scanning apps... $app\033[0K\r" 1>&2
    
    if [ ! -d "${DIRECTORY}/apps/${app}" ];then
      #add to updatable list
      updatable="${updatable}
${app}"
      
    elif [ ! -d "${DIRECTORY}/update/pi-apps/apps/${app}" ];then
      #app only exists locally
      true
      
    elif ! diff -r "${DIRECTORY}/apps/${app}" "${DIRECTORY}/update/pi-apps/apps/${app}" -q >/dev/null ;then
      #if app hashes don't match, add to updatable list
      updatable="${updatable}
${app}"
    fi
  done 
  
  #remove initial newline character
  updatable="${updatable:1}"
  
  echo -e "Scanning apps... Done\033[0K" 1>&2
  
  echo "${updatable}"
elif [ "$1" == 'update-all' ];then

  echo

  #UPDATE-ALL
  #for this operation, a program name cannot be specified.
  IFS=$'\n'
  updatable="$("${DIRECTORY}/manage" check-all)"
  echo "Updatable: ${updatable}EOU"
  for updateapp in $updatable
  do
    status "updating $updateapp"
    #update it using a recursive script instance
    echo "${DIRECTORY}/manage update $updateapp"
    "${DIRECTORY}/manage" update "$updateapp" || exit 1
  done
  status_green "Operation completed successfully!"
else
  error "Invalid argument. Allowed values: 'install', 'multi-install', 'install-if-not-installed', 'uninstall', 'multi-uninstall', 'update','update-all', or 'check-all'."
fi
